package api;

import java.util.Collection;
import java.util.HashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 * User: Darren
 * Date: 8/12/12
 * Time: 10:04 PM
 * Note: Java concurrency knowledge acquired here:
 *  http://docs.oracle.com/javase/tutorial/essential/concurrency/index.html
 */
public class APICallRateMonitor {
    private static final int SECS_PER_MIN = 60;

    public class ApiCallCount {
        public Integer apiCallTotal;
        public int[] apiCallPerSecond;
        ApiCallCount() {
            apiCallTotal = 0;
            apiCallPerSecond = new int[SECS_PER_MIN];
        }
    }

    private HashMap<APICallTypes, ApiCallCount> apiCalls = new HashMap<APICallTypes, ApiCallCount>();
    private int currentInterval = 0;
    private int lastInterval = 1;

    private final Object lock = new Object();

    public APICallRateMonitor(ScheduledExecutorService scheduler) {
        MonitorApiCalls monitor = new MonitorApiCalls();
        final ScheduledFuture<?> monitorScheduler = scheduler.scheduleAtFixedRate(monitor,
                1, 1, TimeUnit.SECONDS);
    }

    private void addApiCall(APICallTypes type) {
        ApiCallCount count = new ApiCallCount();
        apiCalls.put(type, count);
    }

    private ApiCallCount getApiCallCount(APICallTypes type) {
        if (!apiCalls.containsKey(type)) {
             addApiCall(type);
        }
        return apiCalls.get(type);
    }

    public boolean callExceedsLimit(APICallTypes type) {
        synchronized (lock) {
            if (type.getLimit() < 0) {
                return false;
            }
            ApiCallCount count = getApiCallCount(type);
            int incrementedCount = count.apiCallTotal + 1;
            if (incrementedCount > type.getLimit()) {
                return true;
            }
            ++count.apiCallTotal;
            ++count.apiCallPerSecond[currentInterval];
            return false;
        }
    }

    public int getTotal(APICallTypes type) {
        synchronized (lock) {
            if (apiCalls.containsKey(type)) {
                return apiCalls.get(type).apiCallTotal;
            }
            return -1;
        }
    }

    public int[] getCallPerSecond(APICallTypes type) {
        synchronized (lock) {
            if (apiCalls.containsKey(type)) {
                return apiCalls.get(type).apiCallPerSecond;
            }
            return new int[0];
        }
    }

    private class MonitorApiCalls implements Runnable {
        // TODO: if the total drops to zero the monitoring of that call type could be stopped.
        @Override
        public void run() {
            synchronized (lock) {
                if (currentInterval >= SECS_PER_MIN -1) {
                    currentInterval = 0;
                } else {
                    ++currentInterval;
                }
                Collection<ApiCallCount> calls = apiCalls.values();
                for (ApiCallCount call : calls) {
                    call.apiCallTotal -= call.apiCallPerSecond[lastInterval];
                    call.apiCallPerSecond[lastInterval] = 0;
                }
                if (lastInterval >= SECS_PER_MIN -1) {
                    lastInterval = 0;
                } else {
                    ++lastInterval;
                }
            }
        }
    }

}
